import type { RouteLocationNormalized, RouteLocationRaw, Router } from 'vue-router'

import { toRaw, unref } from 'vue'
import { defineStore } from 'pinia'
import { store } from '@/store'

import { useGo, useRedo } from '@/hooks/web/usePage'
import { Persistent } from '@/utils/cache/persistent'

import { PageEnum } from '@/enums/pageEnum'
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@/router/routes/basic'
import { getRawRoute } from '@/utils'
import { MULTIPLE_TABS_KEY } from '@/enums/cacheEnum'

import projectSetting from '@/settings/projectSetting'

export interface MultipleTabState {
  cacheTabList: Set<string>
  tabList: RouteLocationNormalized[]
  lastDragEndIndex: number
}

function handleGotoPage(router: Router) {
  const go = useGo(router)
  go(unref(router.currentRoute).fullPath, true)
}

function getToTarget(tabItem: RouteLocationNormalized) {
  const { params, path, query } = tabItem
  return {
    params: params || {},
    path,
    query: query || {},
  }
}

const cacheTab = projectSetting.multiTabsSetting.cache

export const useMultipleTabStore = defineStore('app-multiple-tab', {
  state: (): MultipleTabState => ({
    // Tabs that need to be cached
    cacheTabList: new Set(),
    // multiple tab list
    tabList: cacheTab ? Persistent.getLocal(MULTIPLE_TABS_KEY) || [] : [],
    // Index of the last moved tab
    lastDragEndIndex: 0,
  }),
  getters: {
    getTabList(state): RouteLocationNormalized[] {
      return state.tabList
    },
    getCachedTabList(state): string[] {
      return Array.from(state.cacheTabList)
    },
    getLastDragEndIndex(state): number {
      return state.lastDragEndIndex
    },
  },
  actions: {
    /**
     * Update the cache according to the currently opened tabs
     */
    updateCacheTab() {
      const cacheMap: Set<string> = new Set()

      for (const tab of this.tabList) {
        const item = getRawRoute(tab)
        // Ignore the cache
        const needCache = !item.meta?.ignoreKeepAlive
        if (!needCache)
          continue

        const name = item.name as string
        cacheMap.add(name)
      }
      this.cacheTabList = cacheMap
    },

    /**
     * Refresh tabs
     */
    async refreshPage(router: Router) {
      const { currentRoute } = router
      const route = unref(currentRoute)
      const name = route.name

      const findTab = this.getCachedTabList.find(item => item === name)
      if (findTab)
        this.cacheTabList.delete(findTab)

      const redo = useRedo(router)
      await redo()
    },
    clearCacheTabs(): void {
      this.cacheTabList = new Set()
    },
    resetState(): void {
      this.tabList = []
      this.clearCacheTabs()
    },
    goToPage(router: Router) {
      const go = useGo(router)
      const len = this.tabList.length
      const { path } = unref(router.currentRoute)

      let toPath: PageEnum | string = PageEnum.BASE_HOME

      if (len > 0) {
        const page = this.tabList[len - 1]
        const p = page.fullPath || page.path
        if (p)
          toPath = p
      }
      // Jump to the current page and report an error
      path !== toPath && go(toPath as PageEnum, true)
    },

    addTab(route: RouteLocationNormalized) {
      const { path, name, fullPath, params, query, meta } = getRawRoute(route)
      // 404  The page does not need to add a tab
      if (
        path === PageEnum.ERROR_PAGE
        || path === PageEnum.BASE_LOGIN
        || !name
        || [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)
      )
        return

      let updateIndex = -1
      // Existing pages, do not add tabs repeatedly
      const tabHasExits = this.tabList.some((tab, index) => {
        updateIndex = index
        return (tab.fullPath || tab.path) === (fullPath || path)
      })

      // If the tab already exists, perform the update operation
      if (tabHasExits) {
        const curTab = toRaw(this.tabList)[updateIndex]
        if (!curTab)
          return

        curTab.params = params || curTab.params
        curTab.query = query || curTab.query
        curTab.fullPath = fullPath || curTab.fullPath
        this.tabList.splice(updateIndex, 1, curTab)
      }
      else {
        // Add tab
        // 获取动态路由打开数，超过 0 即代表需要控制打开数
        const dynamicLevel = meta?.dynamicLevel ?? -1
        if (dynamicLevel > 0) {
          // 如果动态路由层级大于 0 了，那么就要限制该路由的打开数限制了
          // 首先获取到真实的路由，使用配置方式减少计算开销.
          // const realName: string = path.match(/(\S*)\//)![1];
          const realPath = meta?.realPath ?? ''
          // 获取到已经打开的动态路由数, 判断是否大于某一个值
          if (this.tabList.filter(e => e.meta?.realPath ?? realPath === '').length >= dynamicLevel) {
            // 关闭第一个
            const index = this.tabList.findIndex(item => item.meta.realPath === realPath)
            index !== -1 && this.tabList.splice(index, 1)
          }
        }
        this.tabList.push(route)
      }
      this.updateCacheTab()
      cacheTab && Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList)
    },

    async closeTab(tab: RouteLocationNormalized, router: Router) {
      const close = (route: RouteLocationNormalized) => {
        const { fullPath, meta: { affix } = {} } = route
        if (affix)
          return

        const index = this.tabList.findIndex(item => item.fullPath === fullPath)
        index !== -1 && this.tabList.splice(index, 1)
      }

      const { currentRoute, replace } = router

      const { path } = unref(currentRoute)
      if (path !== tab.path) {
        // Closed is not the activation tab
        close(tab)
        this.updateCacheTab()
        return
      }

      // Closed is activated atb
      let toTarget: RouteLocationRaw = {}

      const index = this.tabList.findIndex(item => item.path === path)

      // If the current is the leftmost tab
      if (index === 0) {
        // There is only one tab, then jump to the homepage, otherwise jump to the right tab
        if (this.tabList.length === 1) {
          toTarget = PageEnum.BASE_HOME
        }
        else {
          //  Jump to the right tab
          const page = this.tabList[index + 1]
          toTarget = getToTarget(page)
        }
      }
      else {
        // Close the current tab
        const page = this.tabList[index - 1]
        toTarget = getToTarget(page)
      }
      close(currentRoute.value)
      await replace(toTarget)
    },

    // Close according to key
    async closeTabByKey(key: string, router: Router) {
      const index = this.tabList.findIndex(item => (item.fullPath || item.path) === key)
      if (index !== -1) {
        await this.closeTab(this.tabList[index], router)
        const { currentRoute, replace } = router
        // 检查当前路由是否存在于tabList中
        const isActivated = this.tabList.findIndex((item) => {
          return item.fullPath === currentRoute.value.fullPath
        })
        // 如果当前路由不存在于TabList中，尝试切换到其它路由
        if (isActivated === -1) {
          let pageIndex
          if (index > 0)
            pageIndex = index - 1
          else if (index < this.tabList.length - 1)
            pageIndex = index + 1
          else
            pageIndex = -1

          if (pageIndex >= 0) {
            const page = this.tabList[index - 1]
            const toTarget = getToTarget(page)
            await replace(toTarget)
          }
        }
      }
    },

    // Sort the tabs
    sortTabs(oldIndex: number, newIndex: number) {
      const currentTab = this.tabList[oldIndex]
      this.tabList.splice(oldIndex, 1)
      this.tabList.splice(newIndex, 0, currentTab)
      this.lastDragEndIndex = this.lastDragEndIndex + 1
    },

    // Close the tab on the right and jump
    closeLeftTabs(route: RouteLocationNormalized, router: Router) {
      const index = this.tabList.findIndex(item => item.path === route.path)

      if (index > 0) {
        const leftTabs = this.tabList.slice(0, index)
        const pathList: string[] = []
        for (const item of leftTabs) {
          const affix = item?.meta?.affix ?? false
          if (!affix)
            pathList.push(item.fullPath)
        }
        this.bulkCloseTabs(pathList)
      }
      this.updateCacheTab()
      handleGotoPage(router)
    },

    // Close the tab on the left and jump
    closeRightTabs(route: RouteLocationNormalized, router: Router) {
      const index = this.tabList.findIndex(item => item.fullPath === route.fullPath)

      if (index >= 0 && index < this.tabList.length - 1) {
        const rightTabs = this.tabList.slice(index + 1, this.tabList.length)

        const pathList: string[] = []
        for (const item of rightTabs) {
          const affix = item?.meta?.affix ?? false
          if (!affix)
            pathList.push(item.fullPath)
        }
        this.bulkCloseTabs(pathList)
      }
      this.updateCacheTab()
      handleGotoPage(router)
    },

    closeAllTab(router: Router) {
      this.tabList = this.tabList.filter(item => item?.meta?.affix ?? false)
      this.clearCacheTabs()
      this.goToPage(router)
    },

    /**
     * Close other tabs
     */
    closeOtherTabs(route: RouteLocationNormalized, router: Router) {
      const closePathList = this.tabList.map(item => item.fullPath)

      const pathList: string[] = []

      for (const path of closePathList) {
        if (path !== route.fullPath) {
          const closeItem = this.tabList.find(item => item.fullPath === path)
          if (!closeItem)
            continue

          const affix = closeItem?.meta?.affix ?? false
          if (!affix)
            pathList.push(closeItem.fullPath)
        }
      }
      this.bulkCloseTabs(pathList)
      this.updateCacheTab()
      Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList, true)
      handleGotoPage(router)
    },

    /**
     * Close tabs in bulk
     */
    bulkCloseTabs(pathList: string[]) {
      this.tabList = this.tabList.filter(item => !pathList.includes(item.fullPath))
    },

    /**
     * Set tab's title
     */
    setTabTitle(title: string, route: RouteLocationNormalized) {
      const findTab = this.getTabList.find(item => item === route)
      if (findTab) {
        findTab.meta.title = title
        this.updateCacheTab()
      }
    },
    /**
     * replace tab's path
     *
     */
    updateTabPath(fullPath: string, route: RouteLocationNormalized) {
      const findTab = this.getTabList.find(item => item === route)
      if (findTab) {
        findTab.fullPath = fullPath
        findTab.path = fullPath
        this.updateCacheTab()
      }
    },
  },
})

// Need to be used outside the setup
export function useMultipleTabWithOutStore() {
  return useMultipleTabStore(store)
}
